<?php


namespace Cblood\Container;

use Cblood\Container\Annotation\ReflectionAnnotationClass;
use Cblood\Container\Label\AnnotationLabel;
use ReflectionClass;

class Container
{

    /**
     * 需要持久化的类
     * 格式：array('Cblood\Container\Test' , 'Cblood\Container\Test2')
     * @var array
     */
    protected $persistenceConfig = array();

    /**
     * 用来装持久化的类的实例
     * 格式 array('Cblood\Container\Test' => object)
     * @var
     */
    protected $container = array();

    /**
     * 配置 class类的别名
     * 格式 array('hello' => 'Cblood\Hello')
     * @var array
     */
    protected $classMap = array();

    /**
     * 注入属性的标签名
     * @var string
     */
    protected $parameterLabel = 'Inject';

    /**
     * 判断是否可以实例化
     * @param $className
     * @return bool
     */
    public function isInstance($className)
    {
        if (array_key_exists($className, $this->classMap)) {
            $className = $this->classMap[$className];
        }

        return class_exists($className);
    }

    /**
     * 实例化类
     * @param $className
     * @param array $parameters
     * @return object
     * @throws \Exception
     */
    public function instance($className, $parameters = [])
    {
        if (array_key_exists($className, $this->classMap)) {
            $className = $this->classMap[$className];
        }

        if (array_key_exists($className, $this->container)) {
            return $this->container[$className];
        }

        if (!class_exists($className)) {
            throw new \Exception('找不到对象！');
        }

        $reflectionClass = new \ReflectionClass($className);
        if ($reflectionClass->isInstantiable()) {
            $parameters = $this->searchMethodParameters($reflectionClass, '__construct', $parameters);

            $reflectionAnnotationClass = new ReflectionAnnotationClass($reflectionClass);
            $labels = $reflectionAnnotationClass->getAll();
            $instancesClass = $reflectionClass->newInstanceArgs($parameters);
            $injects = $this->searchParameterLabel($reflectionAnnotationClass);

            foreach ($injects as $parameterName => $value) {
                $propertyClass = $reflectionClass->getProperty($parameterName);
                !$propertyClass->isPublic() && $propertyClass->setAccessible(true);
                $this->isInstance($value) && $value = $this->instance($value);
                $propertyClass->setValue($instancesClass, $value);
            }

            if (in_array($className, $this->persistenceConfig)) {
                $this->container[$className] = $instancesClass;
            }

            /**
             * 执行标签的方法
             * @var AnnotationLabel $object
             */
            foreach ($labels as $objects) {
                foreach ($objects as $object) {
                    $object->execute();
                }
            }

            return $instancesClass;
        }

        throw new \Exception('不是可实例化的对象。。。');
    }

    /**
     * 调用类中的方法
     * @param $object
     * @param $method
     * @param array $parameters
     * @return mixed
     * @throws \Exception
     */
    public function callMethod($object, $method, $parameters = [])
    {
        $reflectionClass = new ReflectionClass($object);
        $parameters = $this->searchMethodParameters($reflectionClass, $method, $parameters);
        if ($reflectionClass->hasMethod($method)) {
            return call_user_func_array([$object, $method], $parameters);
        }

        throw new \Exception($object . ' 不是实例化的对象。。。');
    }

    /**
     * 实例化实体类，并且直接输入属性
     * @param $className
     * @param array $parameters 构造函数需要的参数
     * @param array $properties class的属性的值
     * @return object
     */
    public function entity($className, $properties = [], $parameters = [])
    {
        $instanceClass = $this->instance($className, $parameters);
        $reflectionClass = new ReflectionClass($className);
        foreach ($properties as $name => $value) {
            if ($reflectionClass->hasProperty($name)) {
                $reflectionProperty = $reflectionClass->getProperty($name);
                !$reflectionProperty->isPublic() && $reflectionProperty->setAccessible(true);
                $reflectionProperty->setValue($instanceClass, $value);
            }
        }
        return $instanceClass;
    }

    protected function searchMethodParameters(ReflectionClass &$reflectionClass, $method, $parameters = [])
    {
        if ($reflectionClass->hasMethod($method)) {
            $reflectionMethod = $reflectionClass->getMethod($method);
            $reflectionMethodParameters = $reflectionMethod->getParameters();

            for ($i = count($parameters); $i < $reflectionMethod->getNumberOfParameters(); $i++) {
                if ($reflectionMethodParameters[$i]->hasType() && $reflectionMethodParameters[$i]->getClass()) {
                    $parameters[] = $this->instance($reflectionMethodParameters[$i]->getClass()->getName());
                } elseif ($reflectionMethodParameters[$i]->isDefaultValueAvailable()) {
                    $parameters[] = $reflectionMethodParameters[$i]->getDefaultValue();
                }
            }
        }
        return $parameters;
    }

    /**
     * 获取类中属性的 this->parameterLabel 标签
     * @param ReflectionAnnotationClass $annotationClass
     * @return array
     */
    protected function searchParameterLabel(ReflectionAnnotationClass $annotationClass)
    {
        $result = [];
        $annotationProperties = $annotationClass->getProperties();
        foreach ($annotationProperties as $property) {
            if ($property->hasAnnotation($this->parameterLabel)) {
                $label = $property->getAnnotation($this->parameterLabel);
                $result[$property->getName()] = $label->value;
            }
        }
        return $result;
    }


    /**
     *
     * @param $persistenceConfig
     */
    public function register($persistenceConfig)
    {
        if (empty($this->persistenceConfig)) {
            $this->persistenceConfig = $persistenceConfig;
        } else {
            $this->persistenceConfig = array_merge($this->persistenceConfig, $persistenceConfig);
        }
    }

    /**
     * @param array $classMap
     */
    public function setClassMap($classMap = [])
    {
        $this->classMap = array_merge($this->classMap, $classMap);
    }
}